//--
//-- Standard formatters
//--

config.formatters = [
	{
		name: "table",
		match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
		lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
		rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
		cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
		cellTermRegExp: /((?:\x20*)\|)/mg,
		rowTypes: { "c": "caption", "h": "thead", "": "tbody", "f": "tfoot" },
		handler: function(w)
		{
			var table = createTiddlyElement(w.output, "table", null, "twtable");
			var prevColumns = [];
			var currRowType = null;
			var rowContainer;
			var rowCount = 0;
			var onmouseover = function() { jQuery(this).addClass("hoverRow") };
			var onmouseout = function() { jQuery(this).removeClass("hoverRow") };
			w.nextMatch = w.matchStart;
			this.lookaheadRegExp.lastIndex = w.nextMatch;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
				var nextRowType = lookaheadMatch[2];
				if(nextRowType == "k") {
					table.className = lookaheadMatch[1];
					w.nextMatch += lookaheadMatch[0].length + 1;
				} else {
					if(nextRowType != currRowType) {
						rowContainer = createTiddlyElement(table, this.rowTypes[nextRowType]);
						currRowType = nextRowType;
					}
					if(currRowType == "c") {
						// Caption
						w.nextMatch++;
						if(rowContainer != table.firstChild)
							table.insertBefore(rowContainer, table.firstChild);
						rowContainer.setAttribute("align", rowCount == 0 ? "top" : "bottom");
						w.subWikifyTerm(rowContainer, this.rowTermRegExp);
					} else {
						var theRow = createTiddlyElement(rowContainer, "tr", null, rowCount % 2 ? "oddRow" : "evenRow");
						theRow.onmouseover = onmouseover;
						theRow.onmouseout = onmouseout;
						this.rowHandler(w, theRow, prevColumns);
						rowCount++;
					}
				}
				this.lookaheadRegExp.lastIndex = w.nextMatch;
				lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			}
		},
		rowHandler: function(w, e, prevColumns)
		{
			var col = 0;
			var colSpanCount = 1;
			var prevCell = null;
			this.cellRegExp.lastIndex = w.nextMatch;
			var cellMatch = this.cellRegExp.exec(w.source);
			while(cellMatch && cellMatch.index == w.nextMatch) {
				if(cellMatch[1] == "~") {
					// Rowspan
					var last = prevColumns[col];
					if(last) {
						last.rowSpanCount++;
						last.element.setAttribute("rowspan", last.rowSpanCount);
						last.element.setAttribute("rowSpan", last.rowSpanCount); // Needed for IE
						last.element.valign = "center";
						if(colSpanCount > 1) {
							last.element.setAttribute("colspan", colSpanCount);
							last.element.setAttribute("colSpan", colSpanCount); // Needed for IE
							colSpanCount = 1;
						}
					}
					w.nextMatch = this.cellRegExp.lastIndex - 1;
				} else if(cellMatch[1] == ">") {
					// Colspan
					colSpanCount++;
					w.nextMatch = this.cellRegExp.lastIndex - 1;
				} else if(cellMatch[2]) {
					// End of row
					if(prevCell && colSpanCount > 1) {
						prevCell.setAttribute("colspan", colSpanCount);
						prevCell.setAttribute("colSpan", colSpanCount); // Needed for IE
					}
					w.nextMatch = this.cellRegExp.lastIndex;
					break;
				} else {
					// Cell
					w.nextMatch++;
					var styles = config.formatterHelpers.inlineCssHelper(w);
					var spaceLeft = false;
					var chr = w.source.substr(w.nextMatch, 1);
					while(chr == " ") {
						spaceLeft = true;
						w.nextMatch++;
						chr = w.source.substr(w.nextMatch, 1);
					}
					var cell;
					if(chr == "!") {
						cell = createTiddlyElement(e, "th");
						w.nextMatch++;
					} else {
						var isInsideHeader = e.parentElement.tagName.toLocaleLowerCase() === 'thead';
						cell = createTiddlyElement(e, isInsideHeader ? "th" : "td");
					}
					prevCell = cell;
					prevColumns[col] = { rowSpanCount: 1, element: cell };
					if(colSpanCount > 1) {
						cell.setAttribute("colspan", colSpanCount);
						cell.setAttribute("colSpan", colSpanCount); // Needed for IE
						colSpanCount = 1;
					}
					config.formatterHelpers.applyCssHelper(cell, styles);
					w.subWikifyTerm(cell, this.cellTermRegExp);
					if(w.matchText.substr(w.matchText.length - 2, 1) == " ") // spaceRight
						cell.align = spaceLeft ? "center" : "left";
					else if(spaceLeft)
						cell.align = "right";
					w.nextMatch--;
				}
				col++;
				this.cellRegExp.lastIndex = w.nextMatch;
				cellMatch = this.cellRegExp.exec(w.source);
			}
		}
	},

	{
		name: "heading",
		match: "^!{1,6}",
		termRegExp: /(\n)/mg,
		handler: function(w)
		{
			w.subWikifyTerm(createTiddlyElement(w.output, "h" + w.matchLength), this.termRegExp);
		}
	},

	{
		name: "list",
		match: "^(?:[\\*#;:]+)",
		lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
		termRegExp: /(\n)/mg,
		handler: function(w)
		{
			// stack holds nested elements to which (nested) lists are appended
			var stack = [w.output];
			var currLevel = 0, currType = null;
			var listLevel, listType, itemType, baseType;
			w.nextMatch = w.matchStart;
			this.lookaheadRegExp.lastIndex = w.nextMatch;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
				if(lookaheadMatch[1]) {
					listType = "ul";
					itemType = "li";
				} else if(lookaheadMatch[2]) {
					listType = "ol";
					itemType = "li";
				} else if(lookaheadMatch[3]) {
					listType = "dl";
					itemType = "dt";
				} else if(lookaheadMatch[4]) {
					listType = "dl";
					itemType = "dd";
				}
				if(!baseType)
					baseType = listType;
				listLevel = lookaheadMatch[0].length;
				w.nextMatch += lookaheadMatch[0].length;
				var l;
				if(listLevel > currLevel) {
					for(l = currLevel; l < listLevel; l++) {
						var target = (currLevel == 0) ? stack[stack.length - 1] : stack[stack.length - 1].lastChild;
						stack.push(createTiddlyElement(target, listType));
					}
				} else if(listType != baseType && listLevel == 1) {
					w.nextMatch -= lookaheadMatch[0].length;
					return;
				} else if(listLevel < currLevel) {
					for(l = currLevel; l > listLevel; l--)
						stack.pop();
				} else if(listLevel == currLevel && listType != currType) {
					stack.pop();
					stack.push(createTiddlyElement(stack[stack.length - 1].lastChild, listType));
				}
				currLevel = listLevel;
				currType = listType;
				var itemElement = createTiddlyElement(stack[stack.length - 1], itemType);
				w.subWikifyTerm(itemElement, this.termRegExp);
				this.lookaheadRegExp.lastIndex = w.nextMatch;
				lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			}
		}
	},

	{
		name: "quoteByBlock",
		match: "^<<<\\n",
		termRegExp: /(^<<<(\n|$))/mg,
		element: "blockquote",
		handler: config.formatterHelpers.createElementAndWikify
	},

	{
		name: "quoteByLine",
		match: "^>+",
		lookaheadRegExp: /^>+/mg,
		termRegExp: /(\n)/mg,
		element: "blockquote",
		handler: function(w)
		{
			var stack = [w.output];
			var currLevel = 0;
			var newLevel = w.matchLength;
			var l, matched;
			do {
				if(newLevel > currLevel) {
					for(l = currLevel; l < newLevel; l++)
						stack.push(createTiddlyElement(stack[stack.length - 1], this.element));
				} else if(newLevel < currLevel) {
					for(l = currLevel; l > newLevel; l--)
						stack.pop();
				}
				currLevel = newLevel;
				w.subWikifyTerm(stack[stack.length - 1], this.termRegExp);
				createTiddlyElement(stack[stack.length - 1], "br");
				this.lookaheadRegExp.lastIndex = w.nextMatch;
				var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
				matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
				if(matched) {
					newLevel = lookaheadMatch[0].length;
					w.nextMatch += lookaheadMatch[0].length;
				}
			} while(matched);
		}
	},

	{
		name: "rule",
		match: "^----+$\\n?|<hr ?/?>\\n?",
		handler: function(w)
		{
			createTiddlyElement(w.output, "hr");
		}
	},

	{
		name: "monospacedByLine",
		match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
		element: "pre",
		handler: function(w)
		{
			switch(w.matchText) {
				case "/*{{{*/\n": // CSS
					this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\f*\/\*\}\}\}\*\/$\n?)/mg;
					break;
				case "{{{\n": // monospaced block
					this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\f*\}\}\}$\n?)/mg;
					break;
				case "//{{{\n": // plugin
					this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\f*\/\/\}\}\}$\n?)/mg;
					break;
				case "<!--{{{-->\n": //template
					this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^\f*<!--\}\}\}-->$\n?)/mg;
					break;
				default:
					break;
			}
			config.formatterHelpers.enclosedTextHelper.call(this, w);
		}
	},

	{
		name: "wikifyComment",
		match: "^(?:/\\*\\*\\*|<!---)\\n",
		handler: function(w)
		{
			var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
			w.subWikifyTerm(w.output, termRegExp);
		}
	},

	{
		name: "macro",
		match: "<<",
		lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
		handler: function(w)
		{
			this.lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
				w.nextMatch = this.lookaheadRegExp.lastIndex;
				invokeMacro(w.output, lookaheadMatch[1], lookaheadMatch[2], w, w.tiddler);
			}
		}
	},

	{
		name: "prettyLink",
		match: "\\[\\[",
		lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
		handler: function(w)
		{
			this.lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
				var e;
				var text = lookaheadMatch[1];
				if(lookaheadMatch[3]) {
					// Pretty bracketted link
					var link = lookaheadMatch[3];
					e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link))
						? createExternalLink(w.output, link)
						: createTiddlyLink(w.output, link, false, null, w.isStatic, w.tiddler);
				} else {
					// Simple bracketted link
					e = createTiddlyLink(w.output, text, false, null, w.isStatic, w.tiddler);
				}
				createTiddlyText(e, text);
				w.nextMatch = this.lookaheadRegExp.lastIndex;
			}
		}
	},

	{
		name: "wikiLink",
		match: config.textPrimitives.unWikiLink + "?" + config.textPrimitives.wikiLink,
		handler: function(w)
		{
			if(w.matchText.substr(0, 1) == config.textPrimitives.unWikiLink) {
				w.outputText(w.output, w.matchStart + 1, w.nextMatch);
				return;
			}
			if(w.matchStart > 0) {
				var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict, "mg");
				preRegExp.lastIndex = w.matchStart - 1;
				var preMatch = preRegExp.exec(w.source);
				if(preMatch.index == w.matchStart - 1) {
					w.outputText(w.output, w.matchStart, w.nextMatch);
					return;
				}
			}
			if(w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) {
				var link = createTiddlyLink(w.output, w.matchText, false, null, w.isStatic, w.tiddler);
				w.outputText(link, w.matchStart, w.nextMatch);
			} else {
				w.outputText(w.output, w.matchStart, w.nextMatch);
			}
		}
	},

	{
		name: "urlLink",
		match: config.textPrimitives.urlPattern,
		handler: function(w)
		{
			w.outputText(createExternalLink(w.output, w.matchText), w.matchStart, w.nextMatch);
		}
	},

	{
		name: "image",
		match: "\\[[<>]?[Ii][Mm][Gg]\\[",
		//# [<] sequence below is to avoid lessThan-questionMark sequence so TiddlyWikis can be included in PHP files
		lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
		handler: function(w)
		{
			this.lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
				var container = w.output;
				var link = lookaheadMatch[5];
				if(link) {
					container = config.formatterHelpers.isExternalLink(link)
						? createExternalLink(w.output, link)
						: createTiddlyLink(w.output, link, false, null, w.isStatic, w.tiddler);
					jQuery(container).addClass("imageLink");
				}
				var img = createTiddlyElement(container, "img");
				if(lookaheadMatch[1])
					img.align = "left";
				else if(lookaheadMatch[2])
					img.align = "right";
				if(lookaheadMatch[3]) {
					img.title = lookaheadMatch[3];
					img.setAttribute("alt", lookaheadMatch[3]);
				}
				img.src = lookaheadMatch[4];
				w.nextMatch = this.lookaheadRegExp.lastIndex;
			}
		}
	},

	{
		name: "html",
		match: "<[Hh][Tt][Mm][Ll]>",
		lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
		handler: function(w)
		{
			this.lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
				createTiddlyElement(w.output, "span").innerHTML = lookaheadMatch[1];
				w.nextMatch = this.lookaheadRegExp.lastIndex;
			}
		}
	},

	{
		name: "commentByBlock",
		match: "/%",
		lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
		handler: function(w)
		{
			this.lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
				w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	},

	{
		name: "characterFormat",
		match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{",
		handler: function(w)
		{
			switch(w.matchText) {
				case "''":
					w.subWikifyTerm(w.output.appendChild(document.createElement("strong")), /('')/mg);
					break;
				case "//":
					w.subWikifyTerm(createTiddlyElement(w.output, "em"), /(\/\/)/mg);
					break;
				case "__":
					w.subWikifyTerm(createTiddlyElement(w.output, "u"), /(__)/mg);
					break;
				case "^^":
					w.subWikifyTerm(createTiddlyElement(w.output, "sup"), /(\^\^)/mg);
					break;
				case "~~":
					w.subWikifyTerm(createTiddlyElement(w.output, "sub"), /(~~)/mg);
					break;
				case "--":
					w.subWikifyTerm(createTiddlyElement(w.output, "strike"), /(--)/mg);
					break;
				case "{{{":
					var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
					lookaheadRegExp.lastIndex = w.matchStart;
					var lookaheadMatch = lookaheadRegExp.exec(w.source);
					if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
						createTiddlyElement(w.output, "code", null, null, lookaheadMatch[1]);
						w.nextMatch = lookaheadRegExp.lastIndex;
					}
					break;
			}
		}
	},

	{
		name: "customFormat",
		match: "@@|\\{\\{",
		handler: function(w)
		{
			switch(w.matchText) {
				case "@@":
					var e = createTiddlyElement(w.output, "span");
					var styles = config.formatterHelpers.inlineCssHelper(w);
					if(styles.length == 0)
						e.className = "marked";
					else
						config.formatterHelpers.applyCssHelper(e, styles);
					w.subWikifyTerm(e, /(@@)/mg);
					break;
				case "{{":
					var lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
					lookaheadRegExp.lastIndex = w.matchStart;
					var lookaheadMatch = lookaheadRegExp.exec(w.source);
					if(lookaheadMatch) {
						w.nextMatch = lookaheadRegExp.lastIndex;
						e = createTiddlyElement(w.output, lookaheadMatch[2] == "\n" ? "div" : "span", null, lookaheadMatch[1]);
						w.subWikifyTerm(e, /(\}\}\})/mg);
					}
					break;
			}
		}
	},

	{
		name: "mdash",
		match: "--",
		handler: function(w)
		{
			createTiddlyElement(w.output, "span").innerHTML = "&mdash;";
		}
	},

	{
		name: "lineBreak",
		match: "\\n|<br ?/?>",
		handler: function(w)
		{
			createTiddlyElement(w.output, "br");
		}
	},

	{
		name: "rawText",
		match: "\"{3}|<nowiki>",
		lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
		handler: function(w)
		{
			this.lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
				createTiddlyElement(w.output, "span", null, null, lookaheadMatch[1]);
				w.nextMatch = this.lookaheadRegExp.lastIndex;
			}
		}
	},

	{
		name: "htmlEntitiesEncoding",
		match: "(?:(?:&#?[a-zA-Z0-9]{2,8};|.)" +
			"(?:&#?(?:x0*(?:3[0-6][0-9a-fA-F]|1D[c-fC-F][0-9a-fA-F]|20[d-fD-F][0-9a-fA-F]|FE2[0-9a-fA-F])|0*" +
			"(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+" +
			"|&#?[a-zA-Z0-9]{2,8};)",
		handler: function(w)
		{
			createTiddlyElement(w.output, "span").innerHTML = w.matchText;
		}
	}
];

